/******************************************************************************* * Copyright (c) 2004, 2015 Richard Hoefter and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Richard Hoefter (richard.hoefter@web.de) - initial API and implementation, bug 95300, bug 95297, bug 128104, bug 201180, bug 288830 * IBM Corporation - NLS'ing and incorporating into Eclipse. * - Bug 177833 Class created from combination of all utility classes of contribution * - Bug 267459 Java project with an external jar file from C:\ on the build path throws a NPE during the Ant Buildfile generation. * - bug fixing *******************************************************************************/ package org.eclipse.ant.internal.ui.datatransfer; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.io.StringWriter; import java.net.URI; import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.OutputKeys; import javax.xml.transform.Result; import javax.xml.transform.Source; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerConfigurationException; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.TransformerFactoryConfigurationError; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import org.eclipse.ant.internal.core.IAntCoreConstants; import org.eclipse.ant.internal.ui.AntUIPlugin; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IMarker; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IWorkspace; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IAdaptable; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.core.runtime.OperationCanceledException; import org.eclipse.core.runtime.Path; import org.eclipse.core.runtime.Status; import org.eclipse.core.variables.VariablesPlugin; import org.eclipse.jdt.core.IClasspathEntry; import org.eclipse.jdt.core.IJavaElement; import org.eclipse.jdt.core.IJavaModelMarker; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.IPackageFragmentRoot; import org.eclipse.jdt.core.IType; import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.junit.JUnitCore; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.swt.widgets.Shell; import org.w3c.dom.Document; import org.xml.sax.SAXException; /** * Collection of utility methods to help when exporting to an Ant build file. */ public class ExportUtil { private ExportUtil() { } /** * Get resource from selection. */ public static IResource getResource(ISelection selection) { if (selection instanceof IStructuredSelection) { for (Iterator<IAdaptable> iter = ((IStructuredSelection) selection).iterator(); iter.hasNext();) { IAdaptable adaptable = iter.next(); return adaptable.getAdapter(IResource.class); } } return null; } /** * Get Java project from resource. */ public static IJavaProject getJavaProjectByName(String name) { try { IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(name); if (project.exists()) { return JavaCore.create(project); } } catch (IllegalArgumentException iae) { // do nothing } return null; } /** * Get project root for given project. */ public static String getProjectRoot(IJavaProject project) { if (project == null) { return null; } IResource resource = project.getResource(); if (resource == null) { return null; } IPath location = resource.getLocation(); if (location == null) { return null; } return location.toString(); } /** * Convert Eclipse path to absolute filename. * * @param file * Project root optionally followed by resource name. An absolute path is simply converted to a string. * @return full qualified path */ public static String resolve(IPath file) { if (file == null) { return null; } try { IFile f = ResourcesPlugin.getWorkspace().getRoot().getFile(file); URI uri = f.getLocationURI(); return (uri != null) ? uri.toString() : f.toString(); } catch (IllegalArgumentException e) { // resource is missing String projectName = removePrefix(file.toString(), "/"); //$NON-NLS-1$ IJavaProject project = getJavaProjectByName(projectName); if (project != null) { return getProjectRoot(project); } // project is null because file is not enclosed in a project i.e. // external jar // https://bugs.eclipse.org/bugs/show_bug.cgi?id=267459 return file.toOSString(); } } /** * Get Java project for given root. */ public static IJavaProject getJavaProject(String root) { IPath path = new Path(root); if (path.segmentCount() == 1) { return getJavaProjectByName(root); } IResource resource = ResourcesPlugin.getWorkspace().getRoot().findMember(path); if (resource != null && resource.getType() == IResource.PROJECT) { if (resource.exists()) { return (IJavaProject) JavaCore.create(resource); } } return null; } /** * Remove project root from given project file. */ public static String removeProjectRoot(String file, IProject project) { String res = removePrefix(file, '/' + project.getName() + '/'); if (res.equals('/' + project.getName())) { return "."; //$NON-NLS-1$ } return res; } /** * Remove project root from given project file. * * @param newProjectRoot * replace project root, e.g. with a variable ${project.location} */ public static String replaceProjectRoot(String file, IProject project, String newProjectRoot) { String res = removeProjectRoot(file, project); if (res.equals(".")) //$NON-NLS-1$ { return newProjectRoot; } if (newProjectRoot == null) { return res; } if (!res.equals(file)) { return newProjectRoot + '/' + res; } return res; } /** * Get for given project all directly dependent projects. * * @return set of IJavaProject objects */ public static List<IJavaProject> getClasspathProjects(IJavaProject project) throws JavaModelException { List<IJavaProject> projects = new ArrayList<>(); IClasspathEntry entries[] = project.getRawClasspath(); addClasspathProjects(projects, entries); return sortProjectsUsingBuildOrder(projects); } private static void addClasspathProjects(List<IJavaProject> projects, IClasspathEntry[] entries) { for (int i = 0; i < entries.length; i++) { IClasspathEntry classpathEntry = entries[i]; if (classpathEntry.getContentKind() == IPackageFragmentRoot.K_SOURCE && classpathEntry.getEntryKind() == IClasspathEntry.CPE_PROJECT) { // found required project on build path String subProjectRoot = classpathEntry.getPath().toString(); IJavaProject subProject = getJavaProject(subProjectRoot); // is project available in workspace if (subProject != null) { projects.add(subProject); } } } } /** * Get for given project all directly and indirectly dependent projects. * * @return set of IJavaProject objects */ public static List<IJavaProject> getClasspathProjectsRecursive(IJavaProject project) throws JavaModelException { LinkedList<IJavaProject> result = new LinkedList<>(); getClasspathProjectsRecursive(project, result); return sortProjectsUsingBuildOrder(result); } private static void getClasspathProjectsRecursive(IJavaProject project, LinkedList<IJavaProject> result) throws JavaModelException { List<IJavaProject> projects = getClasspathProjects(project); for (Iterator<IJavaProject> iter = projects.iterator(); iter.hasNext();) { IJavaProject javaProject = iter.next(); if (!result.contains(javaProject)) { result.addFirst(javaProject); getClasspathProjectsRecursive(javaProject, result); // recursion } } } /** * Sort projects according to General -> Workspace -> Build Order. * * @param projects * list of IJavaProject objects * @return list of IJavaProject objects with new order */ private static List<IJavaProject> sortProjectsUsingBuildOrder(List<IJavaProject> javaProjects) { if (javaProjects.isEmpty()) { return javaProjects; } List<IJavaProject> result = new ArrayList<>(javaProjects.size()); IWorkspace workspace = ResourcesPlugin.getWorkspace(); String[] buildOrder = workspace.getDescription().getBuildOrder(); if (buildOrder == null) {// default build order IProject[] projects = new IProject[javaProjects.size()]; int i = 0; for (Iterator<IJavaProject> iter = javaProjects.iterator(); iter.hasNext(); i++) { IJavaProject javaProject = iter.next(); projects[i] = javaProject.getProject(); } IWorkspace.ProjectOrder po = ResourcesPlugin.getWorkspace().computeProjectOrder(projects); projects = po.projects; buildOrder = new String[projects.length]; for (i = 0; i < projects.length; i++) { buildOrder[i] = projects[i].getName(); } } for (int i = 0; i < buildOrder.length && !javaProjects.isEmpty(); i++) { String projectName = buildOrder[i]; for (Iterator<IJavaProject> iter = javaProjects.iterator(); iter.hasNext();) { IJavaProject javaProject = iter.next(); if (javaProject.getProject().getName().equals(projectName)) { result.add(javaProject); iter.remove(); } } } // add any remaining projects not specified in the build order result.addAll(javaProjects); return result; } /** * Returns cyclic dependency marker for a given project. * * <p> * See org.eclipse.jdt.core.tests.model.ClasspathTests.numberOfCycleMarkers. * * @param javaProject * project for which cyclic dependency marker should be found * @return cyclic dependency marker for a given project or <code>null</code> if there is no such marker * @throws CoreException */ public static IMarker getCyclicDependencyMarker(IJavaProject javaProject) throws CoreException { IMarker[] markers = javaProject.getProject().findMarkers(IJavaModelMarker.BUILDPATH_PROBLEM_MARKER, false, IResource.DEPTH_ONE); for (int i = 0; i < markers.length; i++) { IMarker marker = markers[i]; String cycleAttr = (String) marker.getAttribute(IJavaModelMarker.CYCLE_DETECTED); if (cycleAttr != null && cycleAttr.equals("true")) //$NON-NLS-1$ { return marker; } } return null; } /** * Find JUnit tests. Same tests are also returned by Eclipse run configuration wizard. * * @param containerHandle * project, package or source folder */ public static IType[] findTestsInContainer(String containerHandle) { IJavaElement container = JavaCore.create(containerHandle); if (container == null) { return new IType[0]; } try { return JUnitCore.findTestTypes(container, new NullProgressMonitor()); } catch (OperationCanceledException e) { AntUIPlugin.log(e); } catch (CoreException e) { AntUIPlugin.log(e); } return new IType[0]; } /** * Compares projects by project name. */ public static synchronized Comparator<IJavaProject> getJavaProjectComparator() { if (javaProjectComparator == null) { javaProjectComparator = new JavaProjectComparator(); } return javaProjectComparator; } private static Comparator<IJavaProject> javaProjectComparator; private static class JavaProjectComparator implements Comparator<IJavaProject> { @Override public int compare(IJavaProject o1, IJavaProject o2) { IJavaProject j1 = o1; IJavaProject j2 = o2; return j1.getProject().getName().compareTo(j2.getProject().getName()); } } /** * Compares IFile objects. */ public static synchronized Comparator<IFile> getIFileComparator() { if (fileComparator == null) { fileComparator = new IFileComparator(); } return fileComparator; } private static Comparator<IFile> fileComparator; private static class IFileComparator implements Comparator<IFile> { @Override public int compare(IFile o1, IFile o2) { IFile f1 = o1; IFile f2 = o2; return f1.toString().compareTo(f2.toString()); } } /** * Compares IType objects. */ public static synchronized Comparator<IType> getITypeComparator() { if (typeComparator == null) { typeComparator = new TypeComparator(); } return typeComparator; } private static Comparator<IType> typeComparator; private static class TypeComparator implements Comparator<IType> { @Override public int compare(IType o1, IType o2) { IType t1 = o1; IType t2 = o2; return t1.getFullyQualifiedName().compareTo(t2.getFullyQualifiedName()); } } /** * Platform specific newline character(s). */ public static final String NEWLINE = System.getProperty("line.separator"); //$NON-NLS-1$ public static String removePrefix(String s, String prefix) { if (s == null) { return null; } if (s.startsWith(prefix)) { return s.substring(prefix.length()); } return s; } /** * Remove suffix from given string. */ public static String removeSuffix(String s, String suffix) { if (s == null) { return null; } if (s.endsWith(suffix)) { return s.substring(0, s.length() - suffix.length()); } return s; } /** * Remove prefix and suffix from given string. */ public static String removePrefixAndSuffix(String s, String prefix, String suffix) { return removePrefix(removeSuffix(s, suffix), prefix); } /** * Convert document to formatted XML string. */ public static String toString(Document doc) throws TransformerConfigurationException, TransformerFactoryConfigurationError, TransformerException { // NOTE: There are different transformer implementations in the wild, // which are configured differently // regarding the indent size: // Java 1.4: org.apache.xalan.transformer.TransformerIdentityImpl // Java 1.5: // com.sun.org.apache.xalan.internal.xsltc.trax.TransformerImpl StringWriter writer = new StringWriter(); Source source = new DOMSource(doc); Result result = new StreamResult(writer); TransformerFactory factory = TransformerFactory.newInstance(); boolean indentFallback = false; try { // indent using TransformerImpl factory.setAttribute("indent-number", "4"); //$NON-NLS-1$ //$NON-NLS-2$ } catch (IllegalArgumentException e) { // option not supported, set indent size below indentFallback = true; } Transformer transformer = factory.newTransformer(); transformer.setOutputProperty(OutputKeys.INDENT, "yes"); //$NON-NLS-1$ if (indentFallback) { // indent using TransformerIdentityImpl transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4"); //$NON-NLS-1$ //$NON-NLS-2$ } transformer.transform(source, result); return writer.toString(); } /** * Read XML file. */ public static Document parseXmlFile(File file) throws SAXException, IOException, ParserConfigurationException { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setValidating(false); Document doc = factory.newDocumentBuilder().parse(file); return doc; } /** * Read XML string. */ public static Document parseXmlString(String s) throws SAXException, IOException, ParserConfigurationException { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setValidating(false); Document doc = factory.newDocumentBuilder().parse(new ByteArrayInputStream(s.getBytes())); return doc; } /** * Converts collection to a separated string. * * @param c * collection * @param separator * string to separate items * @return collection items separated with given separator */ public static String toString(Collection<String> c, String separator) { StringBuffer b = new StringBuffer(); for (Iterator<String> iter = c.iterator(); iter.hasNext();) { b.append(iter.next()); b.append(separator); } if (c.size() > 0) { b.delete(b.length() - separator.length(), b.length()); } return b.toString(); } /** * Remove duplicates preserving original order. * * @param l * list to remove duplicates from * @return new list without duplicates */ public static List<String> removeDuplicates(List<String> l) { List<String> res = new ArrayList<>(); for (Iterator<String> iter = l.iterator(); iter.hasNext();) { String element = iter.next(); if (!res.contains(element)) { res.add(element); } } return res; } /** * Check if given file exists that was not written by this export. */ public static boolean existsUserFile(String filename) { File buildFile = new File(filename); if (buildFile.exists()) { try (BufferedReader in = new BufferedReader(new FileReader(buildFile))) { int i = BuildFileCreator.WARNING.indexOf(NEWLINE); String warning = BuildFileCreator.WARNING.substring(0, i); String line; while ((line = in.readLine()) != null) { if (line.indexOf(warning) != -1) { return false; } } return true; } catch (FileNotFoundException e) { return false; } catch (IOException e) { return false; } } return false; } /** * Request write access to given file. Depending on the version control plug-in opens a confirm checkout dialog. * * @param shell * parent instance for dialogs * @param file * file to request write access for * @return <code>true</code> if user confirmed checkout */ public static boolean validateEdit(Shell shell, IFile file) { return file.getWorkspace().validateEdit(new IFile[] { file }, shell).isOK(); } /** * Request write access to given files. Depending on the version control plug-in opens a confirm checkout dialog. * * @param shell * parent instance for dialogs * @return <code>IFile</code> objects for which user confirmed checkout * @throws CoreException * thrown if project is under version control, but not connected */ public static Set<IFile> validateEdit(Shell shell, List<IFile> files) throws CoreException { Set<IFile> confirmedFiles = new TreeSet<>(getIFileComparator()); if (files.size() == 0) { return confirmedFiles; } IStatus status = files.get(0).getWorkspace().validateEdit(files.toArray(new IFile[files.size()]), shell); if (status.isMultiStatus() && status.getChildren().length > 0) { for (int i = 0; i < status.getChildren().length; i++) { IStatus statusChild = status.getChildren()[i]; if (statusChild.isOK()) { confirmedFiles.add(files.get(i)); } } } else if (status.isOK()) { for (Iterator<IFile> iterator = files.iterator(); iterator.hasNext();) { IFile file = iterator.next(); confirmedFiles.add(file); } } if (status.getSeverity() == IStatus.ERROR) { // not possible to checkout files: not connected to version // control plugin or hijacked files and made read-only, so // collect error messages provided by validator and re-throw StringBuffer message = new StringBuffer(status.getPlugin() + ": " //$NON-NLS-1$ + status.getMessage() + NEWLINE); if (status.isMultiStatus()) { for (int i = 0; i < status.getChildren().length; i++) { IStatus statusChild = status.getChildren()[i]; message.append(statusChild.getMessage() + NEWLINE); } } throw new CoreException(new Status(IStatus.ERROR, AntUIPlugin.PI_ANTUI, 0, message.toString(), null)); } return confirmedFiles; } /** * Check if given classpath is a reference to the default classpath of the project. Ideal for testing if runtime classpath was customized. */ public static boolean isDefaultClasspath(IJavaProject project, EclipseClasspath classpath) { // default classpath contains exactly the JRE and the project reference List<String> list = removeDuplicates(classpath.rawClassPathEntries); if (list.size() != 2) { return false; } String entry1 = list.get(0); String entry2 = list.get(1); if (!EclipseClasspath.isJreReference(entry1)) { return false; } if (EclipseClasspath.isProjectReference(entry2)) { IJavaProject referencedProject = EclipseClasspath.resolveProjectReference(entry2); if (referencedProject == null) { // project was not loaded in workspace return false; } else if (referencedProject.getProject().getName().equals(project.getProject().getName())) { return true; } } return false; } /** * Add variable/value for Eclipse variable. If given string is no variable, nothing is added. * * @param variable2valueMap * property map to add variable/value * @param s * String which may contain Eclipse variables, e.g. ${project_name} */ public static void addVariable(Map<String, String> variable2valueMap, String s, String projectRoot) { if (s == null || s.equals(IAntCoreConstants.EMPTY_STRING)) { return; } Pattern pattern = Pattern.compile("\\$\\{.*?\\}"); // ${var} //$NON-NLS-1$ Matcher matcher = pattern.matcher(s); while (matcher.find()) { String variable = matcher.group(); String value; try { value = VariablesPlugin.getDefault().getStringVariableManager().performStringSubstitution(variable); } catch (CoreException e) { // cannot resolve variable value = variable; } variable = removePrefixAndSuffix(variable, "${", "}"); //$NON-NLS-1$ //$NON-NLS-2$ // if it is an environment variable, convert to Ant environment // syntax if (variable.startsWith("env_var:")) //$NON-NLS-1$ { value = "env." + variable.substring("env_var:".length()); //$NON-NLS-1$ //$NON-NLS-2$ } File file = new File(value); if (file.exists()) { value = getRelativePath(file.getAbsolutePath(), projectRoot); } variable2valueMap.put(variable, value); } } /** * Returns a path which is equivalent to the given location relative to the specified base path. */ public static String getRelativePath(String otherLocation, String basePath) { IPath location = new Path(otherLocation); IPath base = new Path(basePath); if ((location.getDevice() != null && !location.getDevice().equalsIgnoreCase(base.getDevice())) || !location.isAbsolute()) { return otherLocation; } int baseCount = base.segmentCount(); int count = base.matchingFirstSegments(location); String temp = IAntCoreConstants.EMPTY_STRING; for (int j = 0; j < baseCount - count; j++) { temp += "../"; //$NON-NLS-1$ } String relative = new Path(temp).append(location.removeFirstSegments(count)).toString(); if (relative.length() == 0) { relative = "."; //$NON-NLS-1$ } return relative; } }